
/*-------------------------------------------------------------------------
 *
 * heapam_visibility.c
 *	  堆中存储的元组的可见性规则。
 *
 * 注意：所有的 HeapTupleSatisfies 例程将在看到插入或删除事务
 * 现在已提交或中止（并且安全地设置提示位）时更新元组的
 * "提示"状态位。如果提示位被更改，将在传入的缓冲区上调用
 * MarkBufferDirtyHint。调用者不仅必须持有一个引脚，
 * 还必须至少保持对包含元组的缓冲区的共享缓冲区内容锁。
 *
 * 注意：在使用非 MVCC 快照时，我们必须检查
 * TransactionIdIsInProgress（该函数在 PGPROC 数组中查找）
 * 然后再调用 TransactionIdDidCommit/TransactionIdDidAbort（该函数在
 * pg_xact 中查找）。否则我们可能会遇到竞争条件：我们可能会
 * 认为一个刚提交的事务崩溃了，因为没有成功的测试。
 * xact.c 小心地在将 MyProc->xid 在 PGPROC 数组中取消设置之前
 * 记录了 pg_xact 中的提交/中止。这修复了那个问题，但
 * 这也意味着存在一个窗口，在此期间 TransactionIdIsInProgress 和
 * TransactionIdDidCommit 两者都会返回 true。如果我们只检查
 * TransactionIdDidCommit，我们可能会认为一个元组已提交，
 * 而后续的 GetSnapshotData 调用仍会认为原始事务正在进行中，
 * 这会导致应用级别的不一致。总结就是，我们必须在所有
 * 代码路径中首先检查 TransactionIdIsInProgress，除了几个
 * 我们在观察自己的主事务的子事务，并且因此不能存在任何
 * 竞争条件的情况。
 *
 * 当使用 MVCC 快照时，我们依赖于 XidInMVCCSnapshot 而不是
 * TransactionIdIsInProgress，但逻辑在其他方面是相同的：
 * 不要检查 pg_xact 直到决定 xact 不再进行。
 *
 *
 * 可见性函数摘要：
 *
 *	 HeapTupleSatisfiesMVCC()
 *		  对提供的快照可见，不包括当前命令
 *	 HeapTupleSatisfiesUpdate()
 *		  对即时快照可见，带有用户提供的命令
 *		  计数和更复杂的结果
 *	 HeapTupleSatisfiesSelf()
 *		  对即时快照和当前命令可见
 *	 HeapTupleSatisfiesDirty()
 *		  类似于 HeapTupleSatisfiesSelf()，但包括开放事务
 *	 HeapTupleSatisfiesVacuum()
 *		  对任何运行中的事务可见，用于 VACUUM
 *	 HeapTupleSatisfiesNonVacuumable()
 *		  针对 HeapTupleSatisfiesVacuum 的快照样式 API
 *	 HeapTupleSatisfiesToast()
 *		  可见，除非部分被中断的 VACUUM，用于 TOAST
 *	 HeapTupleSatisfiesAny()
 *		  所有元组都是可见的
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/heap/heapam_visibility.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/subtrans.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "storage/bufmgr.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
#include "utils/combocid.h"
#include "utils/snapmgr.h"


/*
 * SetHintBits()
 *
 * 在元组上设置提交/中止提示位，如果此时适用的话。
 *
 * 只有在我们知道事务的提交记录保证在缓冲区之前刷新到磁盘时，才能安全地设置事务已提交提示位，或者如果表是临时的或未记录的，并且无论如何都将因崩溃而被抹去。我们不能在这里更改页面的 LSN，因为我们可能仅持有缓冲区的共享锁，因此只有在缓冲区的 LSN 已经比提交 LSN 更新时，才能使用 LSN 进行交锁；否则，我们必须只是暂时不设置提示位，直到将来对元组的重新检查。
 *
 * 在标记事务为中止时，我们可以始终设置提示位。（heapam.c 中的一些代码依赖于此！）
 *
 * 此外，如果我们正在清理 HEAP_MOVED_IN 或 HEAP_MOVED_OFF 条目，那么我们始终可以设置提示位，因为 9.0 之前的 VACUUM FULL 始终使用同步提交，并且不移动未被提示的元组。（此子例程并不知道，但其调用者应用了此点。）注意：旧式 VACUUM FULL 已经不存在，但我们必须保持此模块对 MOVED_OFF/MOVED_IN 标志位的支持，只要我们支持来自 9.0 之前数据库的就地更新。
 *
 * 普通提交可能是异步的，因此对于这些情况，我们需要获取事务的 LSN，然后检查这是否已被刷新。
 *
 * 调用者应该传递 xid 作为要检查的事务的 XID，如果不需要检查，则传递 InvalidTransactionId。
 */
static inline void fc_SetHintBits(HeapTupleHeader fc_tuple, Buffer fc_buffer,
			uint16 fc_infomask, TransactionId fc_xid)
{
	if (TransactionIdIsValid(fc_xid))
	{
		/* 注意：此处必须知道 xid 已提交！ */
		XLogRecPtr	fc_commitLSN = TransactionIdGetCommitLSN(fc_xid);

		if (BufferIsPermanent(fc_buffer) && XLogNeedsFlush(fc_commitLSN) &&
			BufferGetLSNAtomic(fc_buffer) < fc_commitLSN)
		{
			/* 未刷新且没有 LSN 交锁，因此不要设置提示 */
			return;
		}
	}

	fc_tuple->t_infomask |= fc_infomask;
	MarkBufferDirtyHint(fc_buffer, true);
}

/*
 * HeapTupleSetHintBits --- SetHintBits() 的导出版本
 *
 * 这必须是分开的，因为 C99 对如何实现内联函数的愚蠢看法。
 */
void HeapTupleSetHintBits(HeapTupleHeader fc_tuple, Buffer fc_buffer,
					 uint16 fc_infomask, TransactionId fc_xid)
{
	fc_SetHintBits(fc_tuple, fc_buffer, fc_infomask, fc_xid);
}


/*
 * HeapTupleSatisfiesSelf
 *		当且仅当堆元组对“自身”是有效的，返回真。
 *
 * 请参见 SNAPSHOT_MVCC 的定义以获取预期行为。
 *
 * 注意：
 *		假设堆元组是有效的。
 *
 * “自身”的满足要求如下：
 *
 * ((Xmin == my-transaction &&				该行由当前事务更新，并且
 *		(Xmax 是空						它没有被删除
 *		 [|| Xmax != my-transaction)])			[或者它被其他事务删除]
 * ||
 *
 * (Xmin 已提交 &&					该行由已提交事务修改，并且
 *		(Xmax 是空 ||					该行没有被删除，或
 *			(Xmax != my-transaction &&			该行被其他事务删除
 *			 Xmax 尚未提交)))			但该事务尚未提交
 */
static bool fc_HeapTupleSatisfiesSelf(HeapTuple fc_htup, Snapshot fc_snapshot, Buffer fc_buffer)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);

	if (!HeapTupleHeaderXminCommitted(fc_tuple))
	{
		if (HeapTupleHeaderXminInvalid(fc_tuple))
			return false;

		/* 由 9.0 之前的二进制升级使用 */
		if (fc_tuple->t_infomask & HEAP_MOVED_OFF)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (TransactionIdIsCurrentTransactionId(fc_xvac))
				return false;
			if (!TransactionIdIsInProgress(fc_xvac))
			{
				if (TransactionIdDidCommit(fc_xvac))
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			}
		}
		/* 由 9.0 之前的二进制升级使用 */
		else if (fc_tuple->t_infomask & HEAP_MOVED_IN)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (!TransactionIdIsCurrentTransactionId(fc_xvac))
			{
				if (TransactionIdIsInProgress(fc_xvac))
					return false;
				if (TransactionIdDidCommit(fc_xvac))
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
								InvalidTransactionId);
				else
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
			}
		}
		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(fc_tuple)))
		{
			if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效 */
				return true;

			if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))	/* 不是删除器 */
				return true;

			if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
			{
				TransactionId fc_xmax;

				fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

				/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
				Assert(TransactionIdIsValid(fc_xmax));

				/* 更新的子事务必须已经中止 */
				if (!TransactionIdIsCurrentTransactionId(fc_xmax))
					return true;
				else
					return false;
			}

			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
			{
				/* 删除的子事务必须已经中止 */
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
							InvalidTransactionId);
				return true;
			}

			return false;
		}
		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(fc_tuple)))
			return false;
		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(fc_tuple)))
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
						HeapTupleHeaderGetRawXmin(fc_tuple));
		else
		{
			/* 它必须已经中止或崩溃 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
						InvalidTransactionId);
			return false;
		}
	}

	/* 到这里时，插入的事务已经提交 */

	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效或已中止 */
		return true;

	if (fc_tuple->t_infomask & HEAP_XMAX_COMMITTED)
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return true;
		return false;			/* 被其他更新 */
	}

	if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		TransactionId fc_xmax;

		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return true;

		fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

		/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
		Assert(TransactionIdIsValid(fc_xmax));

		if (TransactionIdIsCurrentTransactionId(fc_xmax))
			return false;
		if (TransactionIdIsInProgress(fc_xmax))
			return true;
		if (TransactionIdDidCommit(fc_xmax))
			return false;
		/* 它必须已经中止或崩溃 */
		return true;
	}

	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return true;
		return false;
	}

	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(fc_tuple)))
		return true;

	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(fc_tuple)))
	{
		/* 它必须已经中止或崩溃 */
		fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return true;
	}

	/* xmax 事务已提交 */

	if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
	{
		fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return true;
	}

	fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_COMMITTED,
				HeapTupleHeaderGetRawXmax(fc_tuple));
	return false;
}

/*
 * HeapTupleSatisfiesAny
 *		虚拟的 "满足" 例程：任何元组满足 SnapshotAny。
 */
static bool fc_HeapTupleSatisfiesAny(HeapTuple fc_htup, Snapshot fc_snapshot, Buffer fc_buffer)
{
	return true;
}

/*
 * HeapTupleSatisfiesToast
 *		当且仅当堆元组作为 TOAST 行有效时为真。
 *
 * 请参见 SNAPSHOT_TOAST 的定义以获取预期行为。
 *
 * 这是一个简化版本，仅检查 VACUUM 移动条件。
 * 这适用于 TOAST 使用，因为 TOAST 实际上不想进行
 * 自己的时间资格检查；如果您可以看到包含
 * TOAST 引用的主表行，则应该能够看到 TOAST 值。 然而，
 * 清理 TOAST 表与主表是独立的，如果这样的
 * 清理在过程中失败，我们最好做一些检查。
 *
 * 这意味着在 TOAST 表中无法对行进行 UPDATE 操作。
 */
static bool fc_HeapTupleSatisfiesToast(HeapTuple fc_htup, Snapshot fc_snapshot,
						Buffer fc_buffer)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);

	if (!HeapTupleHeaderXminCommitted(fc_tuple))
	{
		if (HeapTupleHeaderXminInvalid(fc_tuple))
			return false;

		/* 由 9.0 之前的二进制升级使用 */
		if (fc_tuple->t_infomask & HEAP_MOVED_OFF)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (TransactionIdIsCurrentTransactionId(fc_xvac))
				return false;
			if (!TransactionIdIsInProgress(fc_xvac))
			{
				if (TransactionIdDidCommit(fc_xvac))
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			}
		}
		/* 由 9.0 之前的二进制升级使用 */
		else if (fc_tuple->t_infomask & HEAP_MOVED_IN)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (!TransactionIdIsCurrentTransactionId(fc_xvac))
			{
				if (TransactionIdIsInProgress(fc_xvac))
					return false;
				if (TransactionIdDidCommit(fc_xvac))
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
								InvalidTransactionId);
				else
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
			}
		}

		/*
		 * 一个无效的 Xmin 可以由一种投机插入留下，
		 * 该插入通过超删除元组被取消。这同样适用于
		 * 在投机插入期间创建的 TOAST 元组。
		 */
		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(fc_tuple)))
			return false;
	}

	/* 否则假定元组对 TOAST 是有效的。 */
	return true;
}

/*
 * HeapTupleSatisfiesUpdate
 *
 *	此函数返回比此文件中大多数函数更详细的结果代码，因为 UPDATE 需要知道的不仅仅是 "是否可见？"。它还允许用户提供 CommandId，而不是依赖于 CurrentCommandId。
 *
 *	可能的返回代码包括：
 *
 *	TM_Invisible：当扫描开始时，元组根本不存在，例如它
 *	是由较晚的 CommandId 创建的。
 *
 *	TM_Ok：元组有效且可见，因此可以更新。
 *
 *	TM_SelfModified：在当前扫描开始后，
 *	当前事务更新了元组。
 *
 *	TM_Updated：元组被已提交的事务更新（包括
 *	元组移动到不同分区的情况）。
 *
 *	TM_Deleted：元组被已提交的事务删除。
 *
 *	TM_BeingModified：元组正在被一个进行中的事务更新，
 *	而该事务与当前事务不同。 （注意：这包括
 *	元组被 MultiXact 共享锁定的情况，即使
 *	该 MultiXact 包括当前事务。想要区分这种情况的调用者
 *	必须自行测试。）
 */
TM_Result HeapTupleSatisfiesUpdate(HeapTuple fc_htup, CommandId fc_curcid,
						 Buffer fc_buffer)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);

	if (!HeapTupleHeaderXminCommitted(fc_tuple))
	{
		if (HeapTupleHeaderXminInvalid(fc_tuple))
			return TM_Invisible;

		/* 由 9.0 之前的二进制升级使用 */
		if (fc_tuple->t_infomask & HEAP_MOVED_OFF)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (TransactionIdIsCurrentTransactionId(fc_xvac))
				return TM_Invisible;
			if (!TransactionIdIsInProgress(fc_xvac))
			{
				if (TransactionIdDidCommit(fc_xvac))
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return TM_Invisible;
				}
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			}
		}
		/* 由 9.0 之前的二进制升级使用 */
		else if (fc_tuple->t_infomask & HEAP_MOVED_IN)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (!TransactionIdIsCurrentTransactionId(fc_xvac))
			{
				if (TransactionIdIsInProgress(fc_xvac))
					return TM_Invisible;
				if (TransactionIdDidCommit(fc_xvac))
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
								InvalidTransactionId);
				else
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return TM_Invisible;
				}
			}
		}
		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(fc_tuple)))
		{
			if (HeapTupleHeaderGetCmin(fc_tuple) >= fc_curcid)
				return TM_Invisible;	/* 在扫描开始后插入 */

			if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效 */
				return TM_Ok;

			if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			{
				TransactionId fc_xmax;

				fc_xmax = HeapTupleHeaderGetRawXmax(fc_tuple);

				/*
				 * 小心：即使这个元组是由我们自己的
				 * 事务创建的，但如果
				 * 我们更新它时原始版本被键共享锁定，
				 * 它可能会被其他事务锁定。
				 */

				if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
				{
					if (MultiXactIdIsRunning(fc_xmax, true))
						return TM_BeingModified;
					else
						return TM_Ok;
				}

				/*
				 * 如果锁已经消失，那么在这个 Xmax 中就没有任何有趣的内容
				 * 剩下；否则，报告这个元组为
				 * 锁定/更新。
				 */
				if (!TransactionIdIsInProgress(fc_xmax))
					return TM_Ok;
				return TM_BeingModified;
			}

			if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
			{
				TransactionId fc_xmax;

				fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

				/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
				Assert(TransactionIdIsValid(fc_xmax));

				/* 删除的子事务必须已经中止 */
				if (!TransactionIdIsCurrentTransactionId(fc_xmax))
				{
					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(fc_tuple),
											 false))
						return TM_BeingModified;
					return TM_Ok;
				}
				else
				{
					if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_curcid)
						return TM_SelfModified; /* 扫描开始后更新 */
					else
						return TM_Invisible;	/* 扫描开始前更新 */
				}
			}

			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
			{
				/* 删除的子事务必须已经中止 */
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
							InvalidTransactionId);
				return TM_Ok;
			}

			if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_curcid)
				return TM_SelfModified; /* 扫描开始后更新 */
			else
				return TM_Invisible;	/* 扫描开始前更新 */
		}
		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(fc_tuple)))
			return TM_Invisible;
		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(fc_tuple)))
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
						HeapTupleHeaderGetRawXmin(fc_tuple));
		else
		{
			/* 它必须已经中止或崩溃 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
						InvalidTransactionId);
			return TM_Invisible;
		}
	}

	/* 到这里时，插入的事务已经提交 */

	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效或已中止 */
		return TM_Ok;

	if (fc_tuple->t_infomask & HEAP_XMAX_COMMITTED)
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return TM_Ok;
		if (!ItemPointerEquals(&fc_htup->t_self, &fc_tuple->t_ctid))
			return TM_Updated;	/* 被其他更新 */
		else
			return TM_Deleted;	/* 被其他事务删除 */
	}

	if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		TransactionId fc_xmax;

		if (HEAP_LOCKED_UPGRADED(fc_tuple->t_infomask))
			return TM_Ok;

		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
		{
			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(fc_tuple), true))
				return TM_BeingModified;

			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
			return TM_Ok;
		}

		fc_xmax = HeapTupleGetUpdateXid(fc_tuple);
		if (!TransactionIdIsValid(fc_xmax))
		{
			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(fc_tuple), false))
				return TM_BeingModified;
		}

		/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
		Assert(TransactionIdIsValid(fc_xmax));

		if (TransactionIdIsCurrentTransactionId(fc_xmax))
		{
			if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_curcid)
				return TM_SelfModified; /* 扫描开始后更新 */
			else
				return TM_Invisible;	/* 扫描开始前更新 */
		}

		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(fc_tuple), false))
			return TM_BeingModified;

		if (TransactionIdDidCommit(fc_xmax))
		{
			if (!ItemPointerEquals(&fc_htup->t_self, &fc_tuple->t_ctid))
				return TM_Updated;
			else
				return TM_Deleted;
		}

		/*
		 * 到这里时，Xmax 中的更新要么已经中止，要么崩溃，但
		 * 其他成员怎么样？
		 */

		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(fc_tuple), false))
		{
			/*
			 * 不再有任何成员，甚至没有锁存活，所以我们可以
			 * 将 Xmax 标记为无效。
			 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
						InvalidTransactionId);
			return TM_Ok;
		}
		else
		{
			/* 有锁存活 */
			return TM_BeingModified;
		}
	}

	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return TM_BeingModified;
		if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_curcid)
			return TM_SelfModified; /* 扫描开始后更新 */
		else
			return TM_Invisible;	/* 扫描开始前更新 */
	}

	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(fc_tuple)))
		return TM_BeingModified;

	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(fc_tuple)))
	{
		/* 它必须已经中止或崩溃 */
		fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return TM_Ok;
	}

	/* xmax 事务已提交 */

	if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
	{
		fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return TM_Ok;
	}

	fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_COMMITTED,
				HeapTupleHeaderGetRawXmax(fc_tuple));
	if (!ItemPointerEquals(&fc_htup->t_self, &fc_tuple->t_ctid))
		return TM_Updated;		/* 被其他更新 */
	else
		return TM_Deleted;		/* 被其他事务删除 */
}

/*
 * HeapTupleSatisfiesDirty
 *		仅当堆元组有效，包括开放事务的影响时，返回真。
 *
 * 请参阅 SNAPSHOT_DIRTY 的定义以了解预期行为。
 *
 * 从当前事务和已提交/已中止的事务的影响来看，这基本上
 * 类似于 HeapTupleSatisfiesSelf。
 * 但是，我们还包括其他仍在进行的事务的影响。
 *
 * 一个特殊的 hack 是，传入的快照结构被用作
 * 输出参数，以返回影响该元组的并行事务的 xids。
 * snapshot->xmin 被设置为元组的 xmin，如果这是另一个
 * 仍在进行中的事务；否则设置为 InvalidTransactionId，如果
 * 元组的 xmin 已经提交好、提交死或我的事务。
 * snapshot->xmax 和元组的 xmax 亦然。如果元组是
 * 以推测方式插入的，意味着插入者可能仍会撤回
 * 插入而不中止整个事务，相关的
 * 令牌也会在 snapshot->speculativeToken 中返回。
 */
static bool fc_HeapTupleSatisfiesDirty(HeapTuple fc_htup, Snapshot fc_snapshot,
						Buffer fc_buffer)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);

	fc_snapshot->xmin = fc_snapshot->xmax = InvalidTransactionId;
	fc_snapshot->speculativeToken = 0;

	if (!HeapTupleHeaderXminCommitted(fc_tuple))
	{
		if (HeapTupleHeaderXminInvalid(fc_tuple))
			return false;

		/* 由 9.0 之前的二进制升级使用 */
		if (fc_tuple->t_infomask & HEAP_MOVED_OFF)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (TransactionIdIsCurrentTransactionId(fc_xvac))
				return false;
			if (!TransactionIdIsInProgress(fc_xvac))
			{
				if (TransactionIdDidCommit(fc_xvac))
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			}
		}
		/* 由 9.0 之前的二进制升级使用 */
		else if (fc_tuple->t_infomask & HEAP_MOVED_IN)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (!TransactionIdIsCurrentTransactionId(fc_xvac))
			{
				if (TransactionIdIsInProgress(fc_xvac))
					return false;
				if (TransactionIdDidCommit(fc_xvac))
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
								InvalidTransactionId);
				else
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
			}
		}
		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(fc_tuple)))
		{
			if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效 */
				return true;

			if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))	/* 不是删除器 */
				return true;

			if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
			{
				TransactionId fc_xmax;

				fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

				/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
				Assert(TransactionIdIsValid(fc_xmax));

				/* 更新的子事务必须已经中止 */
				if (!TransactionIdIsCurrentTransactionId(fc_xmax))
					return true;
				else
					return false;
			}

			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
			{
				/* 删除的子事务必须已经中止 */
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
							InvalidTransactionId);
				return true;
			}

			return false;
		}
		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(fc_tuple)))
		{
			/*
			 * 返回推测性令牌给调用者。调用者可以担心
			 * xmax，因为它需要一个明确锁定的行版本，
			 * 对该元组的并发更新是其目的的冲突。
			 */
			if (HeapTupleHeaderIsSpeculative(fc_tuple))
			{
				fc_snapshot->speculativeToken =
					HeapTupleHeaderGetSpeculativeToken(fc_tuple);

				Assert(fc_snapshot->speculativeToken != 0);
			}

			fc_snapshot->xmin = HeapTupleHeaderGetRawXmin(fc_tuple);
			/* XXX 我们不应该继续查看 xmax 吗？ */
			return true;		/* 被其他事务插入 */
		}
		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(fc_tuple)))
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
						HeapTupleHeaderGetRawXmin(fc_tuple));
		else
		{
			/* 它必须已经中止或崩溃 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
						InvalidTransactionId);
			return false;
		}
	}

	/* 到这里时，插入的事务已经提交 */

	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效或已中止 */
		return true;

	if (fc_tuple->t_infomask & HEAP_XMAX_COMMITTED)
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return true;
		return false;			/* 被其他更新 */
	}

	if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		TransactionId fc_xmax;

		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return true;

		fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

		/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
		Assert(TransactionIdIsValid(fc_xmax));

		if (TransactionIdIsCurrentTransactionId(fc_xmax))
			return false;
		if (TransactionIdIsInProgress(fc_xmax))
		{
			fc_snapshot->xmax = fc_xmax;
			return true;
		}
		if (TransactionIdDidCommit(fc_xmax))
			return false;
		/* 它必须已经中止或崩溃 */
		return true;
	}

	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
	{
		if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			return true;
		return false;
	}

	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(fc_tuple)))
	{
		if (!HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
			fc_snapshot->xmax = HeapTupleHeaderGetRawXmax(fc_tuple);
		return true;
	}

	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(fc_tuple)))
	{
		/* 它必须已经中止或崩溃 */
		fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return true;
	}

	/* xmax 事务已提交 */

	if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
	{
		fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
					InvalidTransactionId);
		return true;
	}

	fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_COMMITTED,
				HeapTupleHeaderGetRawXmax(fc_tuple));
	return false;				/* 被其他更新 */
}

/*
 * HeapTupleSatisfiesMVCC
 *		只有在堆元组对给定的 MVCC 快照是有效时才返回 true。
 *
 * 请参阅 SNAPSHOT_MVCC 的定义以了解预期的行为。
 *
 * 请注意，在这里，如果根据我们的快照，插入/删除事务仍在运行，
 * 即使实际上它已经提交或中止，我们也不会更新元组状态提示位。
 * 这是故意的。
 * 检查真实的事务状态将需要访问高流量共享数据结构，
 * 造成我们不希望出现的争用，并且无论如何它不会改变
 * 我们的可见性检查结果。提示位将由第一个具有足够新快照
 * 以查看插入/删除事务已完成的访问者更新。
 * 与此同时，保持提示位未设置的成本基本上是每次调用
 * HeapTupleSatisfiesMVCC 都需要额外运行 TransactionIdIsCurrentTransactionId
 * 和 XidInMVCCSnapshot（但无论如何必须执行后者）。在旧的
 * 代码中，我们试图尽快设置提示位，而是每次调用时都
 * 执行 TransactionIdIsInProgress --- 但没有任何效果，只要
 * 插入/删除事务仍在运行 --- 这导致更多的周期
 * 和 ProcArrayLock 的更多争用。
 */
static bool fc_HeapTupleSatisfiesMVCC(HeapTuple fc_htup, Snapshot fc_snapshot,
					   Buffer fc_buffer)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);

	if (!HeapTupleHeaderXminCommitted(fc_tuple))
	{
		if (HeapTupleHeaderXminInvalid(fc_tuple))
			return false;

		/* 由 9.0 之前的二进制升级使用 */
		if (fc_tuple->t_infomask & HEAP_MOVED_OFF)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (TransactionIdIsCurrentTransactionId(fc_xvac))
				return false;
			if (!XidInMVCCSnapshot(fc_xvac, fc_snapshot))
			{
				if (TransactionIdDidCommit(fc_xvac))
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			}
		}
		/* 由 9.0 之前的二进制升级使用 */
		else if (fc_tuple->t_infomask & HEAP_MOVED_IN)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (!TransactionIdIsCurrentTransactionId(fc_xvac))
			{
				if (XidInMVCCSnapshot(fc_xvac, fc_snapshot))
					return false;
				if (TransactionIdDidCommit(fc_xvac))
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
								InvalidTransactionId);
				else
				{
					fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
								InvalidTransactionId);
					return false;
				}
			}
		}
		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(fc_tuple)))
		{
			if (HeapTupleHeaderGetCmin(fc_tuple) >= fc_snapshot->curcid)
				return false;	/* 在扫描开始后插入 */

			if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效 */
				return true;

			if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))	/* 不是删除器 */
				return true;

			if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
			{
				TransactionId fc_xmax;

				fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

				/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
				Assert(TransactionIdIsValid(fc_xmax));

				/* 更新的子事务必须已经中止 */
				if (!TransactionIdIsCurrentTransactionId(fc_xmax))
					return true;
				else if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_snapshot->curcid)
					return true;	/* 扫描开始后更新 */
				else
					return false;	/* 扫描开始前更新 */
			}

			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
			{
				/* 删除的子事务必须已经中止 */
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
							InvalidTransactionId);
				return true;
			}

			if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_snapshot->curcid)
				return true;	/* 扫描开始后删除 */
			else
				return false;	/* 扫描开始前删除 */
		}
		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(fc_tuple), fc_snapshot))
			return false;
		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(fc_tuple)))
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
						HeapTupleHeaderGetRawXmin(fc_tuple));
		else
		{
			/* 它必须已经中止或崩溃 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
						InvalidTransactionId);
			return false;
		}
	}
	else
	{
		/* xmin 已提交，但可能不符合我们的快照 */
		if (!HeapTupleHeaderXminFrozen(fc_tuple) &&
			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(fc_tuple), fc_snapshot))
			return false;		/* 视为仍在进行中 */
	}

	/* 到这里时，插入的事务已经提交 */

	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效或已中止 */
		return true;

	if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
		return true;

	if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		TransactionId fc_xmax;

		/* 已在上面检查过 */
		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask));

		fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

		/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
		Assert(TransactionIdIsValid(fc_xmax));

		if (TransactionIdIsCurrentTransactionId(fc_xmax))
		{
			if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_snapshot->curcid)
				return true;	/* 扫描开始后删除 */
			else
				return false;	/* 扫描开始前删除 */
		}
		if (XidInMVCCSnapshot(fc_xmax, fc_snapshot))
			return true;
		if (TransactionIdDidCommit(fc_xmax))
			return false;		/* 更新事务已提交 */
		/* 它必须已经中止或崩溃 */
		return true;
	}

	if (!(fc_tuple->t_infomask & HEAP_XMAX_COMMITTED))
	{
		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(fc_tuple)))
		{
			if (HeapTupleHeaderGetCmax(fc_tuple) >= fc_snapshot->curcid)
				return true;	/* 扫描开始后删除 */
			else
				return false;	/* 扫描开始前删除 */
		}

		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(fc_tuple), fc_snapshot))
			return true;

		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(fc_tuple)))
		{
			/* 它必须已经中止或崩溃 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
						InvalidTransactionId);
			return true;
		}

		/* xmax 事务已提交 */
		fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_COMMITTED,
					HeapTupleHeaderGetRawXmax(fc_tuple));
	}
	else
	{
		/* xmax 已提交，但可能不符合我们的快照 */
		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(fc_tuple), fc_snapshot))
			return true;		/* 视为仍在进行中 */
	}

	/* xmax 事务已提交 */

	return false;
}


/*
 * HeapTupleSatisfiesVacuum
 *
 *	确定元组在VACUUM过程中的状态。这里，我们主要想知道的是一个元组是否可能对*任何*正在运行的事务可见。如果是，它还不能被VACUUM移除。
 *
 * OldestXmin是一个截止的XID（从GetOldestNonRemovableTransactionId()获得）。被XIDs >= OldestXmin删除的元组被视为“最近死亡”；它们可能仍对某些打开的事务可见，因此我们不能移除它们，即使我们看到删除事务已经提交。
 */
HTSV_Result
HeapTupleSatisfiesVacuum(HeapTuple fc_htup, TransactionId fc_OldestXmin,
						 Buffer fc_buffer)
{
	TransactionId fc_dead_after = InvalidTransactionId;
	HTSV_Result fc_res;

	fc_res = HeapTupleSatisfiesVacuumHorizon(fc_htup, fc_buffer, &fc_dead_after);

	if (fc_res == HEAPTUPLE_RECENTLY_DEAD)
	{
		Assert(TransactionIdIsValid(fc_dead_after));

		if (TransactionIdPrecedes(fc_dead_after, fc_OldestXmin))
			fc_res = HEAPTUPLE_DEAD;
	}
	else
		Assert(!TransactionIdIsValid(fc_dead_after));

	return fc_res;
}

/*
 * HeapTupleSatisfiesVacuum和类似例程的工作马。
 *
 * 与HeapTupleSatisfiesVacuum相比，当遇到可能仍对某个后端可见的元组时，该例程会将需与*dead_after中的时间线进行比较的xid存储，并返回HEAPTUPLE_RECENTLY_DEAD。调用者可以随后与时间线进行比较。这在与不同时间线进行比较时是有用的。
 *
 * 注意：HEAPTUPLE_DEAD仍然可能会在这里返回，例如，如果插入事务已中止。
 */
HTSV_Result
HeapTupleSatisfiesVacuumHorizon(HeapTuple fc_htup, Buffer fc_buffer, TransactionId *fc_dead_after)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);
	Assert(fc_dead_after != NULL);

	*fc_dead_after = InvalidTransactionId;

	/*
	 * 插入事务是否已提交？
	 *
	 * 如果插入事务已中止，则该元组从未对任何其他事务可见，因此我们可以立即删除它。
	 */
	if (!HeapTupleHeaderXminCommitted(fc_tuple))
	{
		if (HeapTupleHeaderXminInvalid(fc_tuple))
			return HEAPTUPLE_DEAD;
		/* 由 9.0 之前的二进制升级使用 */
		else if (fc_tuple->t_infomask & HEAP_MOVED_OFF)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (TransactionIdIsCurrentTransactionId(fc_xvac))
				return HEAPTUPLE_DELETE_IN_PROGRESS;
			if (TransactionIdIsInProgress(fc_xvac))
				return HEAPTUPLE_DELETE_IN_PROGRESS;
			if (TransactionIdDidCommit(fc_xvac))
			{
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
							InvalidTransactionId);
				return HEAPTUPLE_DEAD;
			}
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
						InvalidTransactionId);
		}
		/* 由 9.0 之前的二进制升级使用 */
		else if (fc_tuple->t_infomask & HEAP_MOVED_IN)
		{
			TransactionId fc_xvac = HeapTupleHeaderGetXvac(fc_tuple);

			if (TransactionIdIsCurrentTransactionId(fc_xvac))
				return HEAPTUPLE_INSERT_IN_PROGRESS;
			if (TransactionIdIsInProgress(fc_xvac))
				return HEAPTUPLE_INSERT_IN_PROGRESS;
			if (TransactionIdDidCommit(fc_xvac))
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
							InvalidTransactionId);
			else
			{
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
							InvalidTransactionId);
				return HEAPTUPLE_DEAD;
			}
		}
		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(fc_tuple)))
		{
			if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid 无效 */
				return HEAPTUPLE_INSERT_IN_PROGRESS;
			/* 仅锁定？首先运行信息掩码检查，以提高性能 */
			if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask) ||
				HeapTupleHeaderIsOnlyLocked(fc_tuple))
				return HEAPTUPLE_INSERT_IN_PROGRESS;
			/* 由同一事务插入然后删除 */
			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(fc_tuple)))
				return HEAPTUPLE_DELETE_IN_PROGRESS;
			/* 删除的子事务必须已经中止 */
			return HEAPTUPLE_INSERT_IN_PROGRESS;
		}
		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(fc_tuple)))
		{
			/*
			 * 通过查看xmax，在这里可以区分正在进行的INSERT/DELETE，但这对大多数调用者似乎没有益处，甚至对某些人有害。我们更希望调用者查看/等待xmin而不是xmax。返回INSERT_IN_PROGRESS始终是正确的，因为从其他后端的视角来看就是这样的。
			 */
			return HEAPTUPLE_INSERT_IN_PROGRESS;
		}
		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(fc_tuple)))
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_COMMITTED,
						HeapTupleHeaderGetRawXmin(fc_tuple));
		else
		{
			/*
			 * 不在进行中，不已提交，因此要么已中止，要么崩溃
			 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMIN_INVALID,
						InvalidTransactionId);
			return HEAPTUPLE_DEAD;
		}

		/*
		 * 此时xmin已知为已提交，但我们可能还未能设置提示位；因此我们无法再断言它已被设置。
		 */
	}

	/*
	 * 好吧，插入者已经提交，因此在某个时候是好的。现在，删除事务怎么样？
	 */
	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)
		return HEAPTUPLE_LIVE;

	if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
	{
		/*
		 * “删除”事务实际上只是锁定了它，因此无论如何元组都是活的。然而，我们应该确保一旦事务结束，要么设置XMAX_COMMITTED，要么设置XMAX_INVALID，以减少将来检查元组的成本。
		 */
		if (!(fc_tuple->t_infomask & HEAP_XMAX_COMMITTED))
		{
			if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
			{
				/*
				 * 如果这是一个升级前的元组，则多重事务不可能正在运行；否则必须检查。
				 */
				if (!HEAP_LOCKED_UPGRADED(fc_tuple->t_infomask) &&
					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(fc_tuple),
										 true))
					return HEAPTUPLE_LIVE;
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
			}
			else
			{
				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(fc_tuple)))
					return HEAPTUPLE_LIVE;
				fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
							InvalidTransactionId);
			}
		}

		/*
		 * 我们并不关心xmax是提交、已中止还是崩溃。我们知道xmax确实锁定了元组，但它没有且将来也不会实际更新它。
		 */

		return HEAPTUPLE_LIVE;
	}

	if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		TransactionId fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

		/* 已在上面检查过 */
		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask));

		/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
		Assert(TransactionIdIsValid(fc_xmax));

		if (TransactionIdIsInProgress(fc_xmax))
			return HEAPTUPLE_DELETE_IN_PROGRESS;
		else if (TransactionIdDidCommit(fc_xmax))
		{
			/*
			 * multixact 可能仍在运行由于锁定者。需要
			 * 允许修剪，如果低于 xid 界限，无论如何 --
			 * 否则我们可能会得到一个元组，其中更新者必须
			 * 被移除由于界限，但并没有被修剪掉。这
			 * 修剪那元组不是问题，因为任何剩余
			 * 锁定者在更新元组版本中也会存在。
			 */
			*fc_dead_after = fc_xmax;
			return HEAPTUPLE_RECENTLY_DEAD;
		}
		else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(fc_tuple), false))
		{
			/*
			 * 不在进行中，不已提交，因此要么被中止或崩溃。
			 * 将 Xmax 标记为无效。
			 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
		}

		return HEAPTUPLE_LIVE;
	}

	if (!(fc_tuple->t_infomask & HEAP_XMAX_COMMITTED))
	{
		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(fc_tuple)))
			return HEAPTUPLE_DELETE_IN_PROGRESS;
		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(fc_tuple)))
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_COMMITTED,
						HeapTupleHeaderGetRawXmax(fc_tuple));
		else
		{
			/*
			 * 不在进行中，不已提交，因此要么已中止，要么崩溃
			 */
			fc_SetHintBits(fc_tuple, fc_buffer, HEAP_XMAX_INVALID,
						InvalidTransactionId);
			return HEAPTUPLE_LIVE;
		}

		/*
		 * 此时 xmax 确定已提交，但我们可能尚未
		 * 能够设置提示位；所以我们不能再断言它
		 * 已设置。
		 */
	}

	/*
	 * 删除者已提交，允许调用者检查它是否足够近期，以至于
	 * 一些开放事务仍然可以看到元组。
	 */
	*fc_dead_after = HeapTupleHeaderGetRawXmax(fc_tuple);
	return HEAPTUPLE_RECENTLY_DEAD;
}


/*
 * HeapTupleSatisfiesNonVacuumable
 *
 *	如果元组可能对某个事务可见，则返回 true；如果它
 *	肯定对所有人来说都是死亡的，即，可清空。
 *
 *	请参阅 SNAPSHOT_NON_VACUUMABLE 的定义以获取预期行为。
 *
 *	这是一个可以通过 HeapTupleSatisfiesSnapshot 调用的 HeapTupleSatisfiesVacuum 接口，
 *	因此可以通过快照使用。snapshot->vistest 必须已设置好以使用的界限。
 */
static bool fc_HeapTupleSatisfiesNonVacuumable(HeapTuple fc_htup, Snapshot fc_snapshot,
								Buffer fc_buffer)
{
	TransactionId fc_dead_after = InvalidTransactionId;
	HTSV_Result fc_res;

	fc_res = HeapTupleSatisfiesVacuumHorizon(fc_htup, fc_buffer, &fc_dead_after);

	if (fc_res == HEAPTUPLE_RECENTLY_DEAD)
	{
		Assert(TransactionIdIsValid(fc_dead_after));

		if (GlobalVisTestIsRemovableXid(fc_snapshot->vistest, fc_dead_after))
			fc_res = HEAPTUPLE_DEAD;
	}
	else
		Assert(!TransactionIdIsValid(fc_dead_after));

	return fc_res != HEAPTUPLE_DEAD;
}


/*
 * HeapTupleIsSurelyDead
 *
 *	廉价地判断一个元组是否对所有旁观者肯定是死亡的。
 *	我们有时在元组刚由另一个可见性例程（通常
 *	是 HeapTupleSatisfiesMVCC）测试时使用此方法，因此，
 *	任何可以设置的提示位应该已经被设置。我们假设，如果没有设置
 *	提示位，xmin 或 xmax 事务仍在运行。因此这比
 *	HeapTupleSatisfiesVacuum 更快，因为我们既不查询 procarray 也不查询 CLOG。
 *	在有疑问时返回 false 是可以的，但只有在元组可以移除时，我们必须返回 true。
 */
bool HeapTupleIsSurelyDead(HeapTuple fc_htup, GlobalVisState *fc_vistest)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);

	/*
	 * 如果插入事务被标记为无效，那么它已中止，并且
	 * 元组肯定是死亡的。如果标记为既不是已提交也不是
	 * 无效，则我们假设它仍然存活（因为假设是
	 * 所有相关提示位刚刚在几分钟前设置）。
	 */
	if (!HeapTupleHeaderXminCommitted(fc_tuple))
		return HeapTupleHeaderXminInvalid(fc_tuple);

	/*
	 * 如果插入事务已提交，但任何删除事务
	 * 已中止，元组仍然存活。
	 */
	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)
		return false;

	/*
	 * 如果 XMAX 只是一个锁，元组仍然存活。
	 */
	if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
		return false;

	/*
	 * 如果 Xmax 是一个 MultiXact，它可能是死的或活的，但我们无法
	 * 确定而不检查 pg_multixact。
	 */
	if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
		return false;

	/* 如果删除者未知是否已提交，则假设它仍在运行。 */
	if (!(fc_tuple->t_infomask & HEAP_XMAX_COMMITTED))
		return false;

	/* 删除者已提交，因此如果 XID 足够旧，元组就是死亡的。 */
	return GlobalVisTestIsRemovableXid(fc_vistest,
									   HeapTupleHeaderGetRawXmax(fc_tuple));
}

/*
 * 这个元组真的只有锁定吗？也就是说，它没有被更新吗？
 *
 * 如果锁定者不是多重事务，检查 infomask 位很简单；但
 * 否则我们需要验证更新事务是否没有中止。
 *
 * 这个函数在这里是因为它遵循了
 * 本文件顶部制定的相同可见性规则。
 */
bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader fc_tuple)
{
	TransactionId fc_xmax;

	/* 如果没有有效的 Xmax，那么显然也没有更新 */
	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)
		return true;

	if (fc_tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
		return true;

	/* 无效的 xmax 意味着没有更新 */
	if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(fc_tuple)))
		return true;

	/*
	 * 如果 HEAP_XMAX_LOCK_ONLY 没有被设置并且不是多重事务，那么这一定
	 * 是已经更新过的
	 */
	if (!(fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI))
		return false;

	/* ... 但如果它是多重事务，那么可能更新的 Xid 已经中止。 */
	fc_xmax = HeapTupleGetUpdateXid(fc_tuple);

	/* 不是 LOCKED_ONLY，因此必须有一个 xmax */
	Assert(TransactionIdIsValid(fc_xmax));

	if (TransactionIdIsCurrentTransactionId(fc_xmax))
		return false;
	if (TransactionIdIsInProgress(fc_xmax))
		return false;
	if (TransactionIdDidCommit(fc_xmax))
		return false;

	/*
	 * 不当前，不在进行中，不已提交 -- 一定是中止或
	 * 崩溃
	 */
	return true;
}

/*
 * 检查事务 id 'xid' 是否在预排序数组 'xip' 中。
 */
static bool fc_TransactionIdInArray(TransactionId fc_xid, TransactionId *fc_xip, Size fc_num)
{
	return fc_num > 0 &&
		bsearch(&fc_xid, fc_xip, fc_num, sizeof(TransactionId), xidComparator) != NULL;
}

/*
 * 请参阅 HeapTupleSatisfiesMVCC 的注释，以了解此函数遵循的语义。
 *
 * 仅适用于来自目录表的元组！
 *
 * 我们不需要支持 HEAP_MOVED_(IN|OFF)，因为我们仅支持
 * 读取无法在旧版本中创建的目录页。
 *
 * 我们在这里不设置任何提示位，因为这似乎不太可能有帮助，
 * 这些位应该已经由正常访问设置，并且在时间旅行期间这样做的语义
 * 比仅处理当前时刻要复杂得多。
 */
static bool fc_HeapTupleSatisfiesHistoricMVCC(HeapTuple fc_htup, Snapshot fc_snapshot,
							   Buffer fc_buffer)
{
	HeapTupleHeader fc_tuple = fc_htup->t_data;
	TransactionId fc_xmin = HeapTupleHeaderGetXmin(fc_tuple);
	TransactionId fc_xmax = HeapTupleHeaderGetRawXmax(fc_tuple);

	Assert(ItemPointerIsValid(&fc_htup->t_self));
	Assert(fc_htup->t_tableOid != InvalidOid);

	/* 插入的事务已中止 */
	if (HeapTupleHeaderXminInvalid(fc_tuple))
	{
		Assert(!TransactionIdDidCommit(fc_xmin));
		return false;
	}
	/* 检查它是否是我们的 txids，顶级事务也在其中 */
	else if (fc_TransactionIdInArray(fc_xmin, fc_snapshot->subxip, fc_snapshot->subxcnt))
	{
		bool		fc_resolved;
		CommandId	fc_cmin = HeapTupleHeaderGetRawCommandId(fc_tuple);
		CommandId	fc_cmax = InvalidCommandId;

		/*
		 * 另一个事务可能已经（尝试）删除了这个元组或
		 * cmin/cmax 存储在组合 CID 中。所以我们需要查找
		 * 实际的值。
		 */
		fc_resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), fc_snapshot,
												 fc_htup, fc_buffer,
												 &fc_cmin, &fc_cmax);

		/*
		 * 如果我们还没有将组合 CID 解析为 cmin/cmax，这意味着我们
		 * 还没有解码组合 CID。这意味着 cmin 肯定在未来，
		 * 我们不应该看到这个元组。
		 *
		 * XXX 这仅适用于正在进行的事务的解码。
		 * 在常规逻辑解码中，我们只在提交时执行此代码，
		 * 到那时我们应该已经看到了所有相关的组合 CID。所以
		 * 理想情况下，我们应该在这种情况下出错，但实际上，
		 * 这不会发生。如果我们对此过于担心，可以在
		 * ResolveCminCmaxDuringDecoding 中添加一个 elog。
		 *
		 * XXX 对于流式情况，我们可以跟踪分配的最大组合 CID，
		 * 并基于此出错（当无法解析观察到的最大值以下的组合
		 * CID 时）。
		 */
		if (!fc_resolved)
			return false;

		Assert(fc_cmin != InvalidCommandId);

		if (fc_cmin >= fc_snapshot->curcid)
			return false;		/* 在扫描开始后插入 */
		/* fall through */
	}
	/* 在我们的 xmin 范围之前提交。进行正常的可见性检查。 */
	else if (TransactionIdPrecedes(fc_xmin, fc_snapshot->xmin))
	{
		Assert(!(HeapTupleHeaderXminCommitted(fc_tuple) &&
				 !TransactionIdDidCommit(fc_xmin)));

		/* 首先检查提示位，然后咨询 clog */
		if (!HeapTupleHeaderXminCommitted(fc_tuple) &&
			!TransactionIdDidCommit(fc_xmin))
			return false;
		/* fall through */
	}
	/* 超过我们的 xmax 范围，即不可见 */
	else if (TransactionIdFollowsOrEquals(fc_xmin, fc_snapshot->xmax))
	{
		return false;
	}
	/* 检查它是否是 [xmin, xmax) 中的已提交事务 */
	else if (fc_TransactionIdInArray(fc_xmin, fc_snapshot->xip, fc_snapshot->xcnt))
	{
		/* fall through */
	}

	/*
	 * 上述情况都不符合，即在[xmin, xmax)之间但尚未提交。即
	 * 不可见。
	 */
	else
	{
		return false;
	}

	/* 此时我们知道xmin是可见的，继续检查xmax */

	/* xid 无效或已中止 */
	if (fc_tuple->t_infomask & HEAP_XMAX_INVALID)
		return true;
	/* 锁定的元组始终是可见的 */
	else if (HEAP_XMAX_IS_LOCKED_ONLY(fc_tuple->t_infomask))
		return true;

	/*
	 * 如果我们查看的是用户表，或如果有人
	 * SELECT ... FOR SHARE/UPDATE系统表，我们可以在这里看到多版本。
	 */
	else if (fc_tuple->t_infomask & HEAP_XMAX_IS_MULTI)
	{
		fc_xmax = HeapTupleGetUpdateXid(fc_tuple);
	}

	/* 检查它是否是我们的 txids，顶级事务也在其中 */
	if (fc_TransactionIdInArray(fc_xmax, fc_snapshot->subxip, fc_snapshot->subxcnt))
	{
		bool		fc_resolved;
		CommandId	fc_cmin;
		CommandId	fc_cmax = HeapTupleHeaderGetRawCommandId(fc_tuple);

		/* 查找实际的cmin/cmax值 */
		fc_resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), fc_snapshot,
												 fc_htup, fc_buffer,
												 &fc_cmin, &fc_cmax);

		/*
		 * 如果我们尚未将组合CID解决为cmin/cmax，这意味着我们
		 * 尚未解码组合CID。这意味着cmax
		 * 绝对在未来，我们仍然应该看到
		 * 元组。
		 *
		 * XXX 这仅适用于正在进行的事务的解码。在
		 * 常规逻辑解码中，我们只在提交时执行此代码，
		 * 到那时我们应该已经看到所有相关组合CID。因此
		 * 理想情况下，我们应该在这种情况下出错，但实际上不会发生。
		 * 如果我们对此过于担心，则可以在ResolveCminCmaxDuringDecoding中添加一个elog。
		 *
		 * XXX 对于流式处理情况，我们可以跟踪分配的最大组合CID，
		 * 并根据此出错（当无法解析组合
		 * CID低于观察到的最大值时）。
		 */
		if (!fc_resolved || fc_cmax == InvalidCommandId)
			return true;

		if (fc_cmax >= fc_snapshot->curcid)
			return true;		/* 扫描开始后删除 */
		else
			return false;		/* 扫描开始前删除 */
	}
	/* 低于xmin边界，正常事务状态有效 */
	else if (TransactionIdPrecedes(fc_xmax, fc_snapshot->xmin))
	{
		Assert(!(fc_tuple->t_infomask & HEAP_XMAX_COMMITTED &&
				 !TransactionIdDidCommit(fc_xmax)));

		/* 首先检查提示位 */
		if (fc_tuple->t_infomask & HEAP_XMAX_COMMITTED)
			return false;

		/* 检查clog */
		return !TransactionIdDidCommit(fc_xmax);
	}
	/* 超过xmax边界，我们根本无法看到删除事务 */
	else if (TransactionIdFollowsOrEquals(fc_xmax, fc_snapshot->xmax))
		return true;
	/* xmax在[xmin, xmax)之间，检查已知提交数组 */
	else if (fc_TransactionIdInArray(fc_xmax, fc_snapshot->xip, fc_snapshot->xcnt))
		return false;
	/* xmax在[xmin, xmax)之间，但已知尚未提交 */
	else
		return true;
}

/*
 * HeapTupleSatisfiesVisibility
 *		仅当堆元组满足时间条件时为真。
 *
 * 注意：
 *	假设堆元组是有效的，并且缓冲区至少被共享锁定。
 *
 *	HeapTuple的t_infomask中的提示位可能会作为副作用被更新；
 *	如果是这样，则指示的缓冲区被标记为脏。
 */
bool HeapTupleSatisfiesVisibility(HeapTuple fc_tup, Snapshot fc_snapshot, Buffer fc_buffer)
{
	switch (fc_snapshot->snapshot_type)
	{
		case SNAPSHOT_MVCC:
			return fc_HeapTupleSatisfiesMVCC(fc_tup, fc_snapshot, fc_buffer);
			break;
		case SNAPSHOT_SELF:
			return fc_HeapTupleSatisfiesSelf(fc_tup, fc_snapshot, fc_buffer);
			break;
		case SNAPSHOT_ANY:
			return fc_HeapTupleSatisfiesAny(fc_tup, fc_snapshot, fc_buffer);
			break;
		case SNAPSHOT_TOAST:
			return fc_HeapTupleSatisfiesToast(fc_tup, fc_snapshot, fc_buffer);
			break;
		case SNAPSHOT_DIRTY:
			return fc_HeapTupleSatisfiesDirty(fc_tup, fc_snapshot, fc_buffer);
			break;
		case SNAPSHOT_HISTORIC_MVCC:
			return fc_HeapTupleSatisfiesHistoricMVCC(fc_tup, fc_snapshot, fc_buffer);
			break;
		case SNAPSHOT_NON_VACUUMABLE:
			return fc_HeapTupleSatisfiesNonVacuumable(fc_tup, fc_snapshot, fc_buffer);
			break;
	}

	return false;				/* 保持编译器安静 */
}
